/**
 * SlotDuino
 * 
 * Copyright (c) 2011/2012 Thiago Hummel Moreira, Ricardo Mizuuti, Rafael Mufato, Bruno Sakai, Andre Turina
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
using System;
using System.Collections;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;

namespace SlotDaemon
{
    /// <summary>
    /// This class is a singleton and is responsible for controling the track.
    /// Any external comunication should be done through it.
    /// Use the static method getInstance() to get the controller instance
    /// </summary>
    public class TrackController
    {
        static private Cpu.Pin[] _avaliablePins = new Cpu.Pin[] {
            Pins.GPIO_PIN_D0,
            Pins.GPIO_PIN_D1,
            Pins.GPIO_PIN_D2,
            Pins.GPIO_PIN_D3,
            Pins.GPIO_PIN_D4,
            Pins.GPIO_PIN_D5,
            Pins.GPIO_PIN_D6,
            Pins.GPIO_PIN_D7
        };

        #region Singleton related
        /// <summary>
        /// This will hold the only one instance of this class
        /// </summary>
        private static TrackController instance = null;

        /// <summary>
        /// This function returns the instance of the TrackController class
        /// </summary>
        /// <returns>The track controller instance</returns>
        public static TrackController getInstance()
        {
            if (null == instance)
            {
                ///This will try to load the config from SD
                ///if it fails it will get an unconfigured track
                instance = new TrackController(TrackConfig.LoadConfig());
            }
            return instance;
        }
        #endregion

        #region Private attributes
        private TrackConfig _cfg;
        private VoltageController _voltageController;
        private Lane[] _lanes;
        private ArrayList _races = new ArrayList();
        #endregion

        #region Public properties
        public string Name { get { return _cfg.Name; } }
        public int Length { get { return _cfg.Length; } }
        public Lane[] Lanes { get { return _lanes; } }
        public byte FreeLanes
        {
            get
            {
                byte free = 0;
                foreach (Lane lane in _lanes)
                    if (lane.IsIdle) free++;
                return free;
            }
        }
        #endregion

        #region Public events
        public event UpdateEventHandler OnUpdate;
        #endregion

        #region Constructors
        private TrackController(TrackConfig config)
        {
            _voltageController = new VoltageController();

            SetConfig(config);
        }
        #endregion

        public void SetConfig(TrackConfig config)
        {
            //Store the TrackConfig
            _cfg = config;

            //Set the VoltageController configuration
            _voltageController.SetConfig(config);

            ///Initialize the track lanes
            _lanes = InitLanes(config.LaneCount);
        }

        private Lane[] InitLanes(byte laneCount)
        {
            Lane[] lanes = new Lane[laneCount];
            for (byte i = 0; i < laneCount; i++)
            {
                //Create the lane
                lanes[i] = new Lane(i, _avaliablePins[i]);

                //Register the OnBeforeVoltageChange handler from the voltage controller
                lanes[i].OnBeforeVoltageChange += new BeforeVoltageChangeEventHandler(_voltageController.ChangeLaneVoltage);

                //Register the LapCompleted handler
                lanes[i].OnLapCompleted += new LapCompletedEventHandler(OnLapCompleted);
            }

            return lanes;
        }

        private void Update(UpdateEventArgs args)
        {
            UpdateEventHandler handler = OnUpdate;
            if (null != handler)
                handler(this, args);
        }

        /// <summary>
        /// Returns the minimum tick number for a lap
        /// </summary>
        /// <returns>The number of ticks of the fastest acceptable lap</returns>
        public long MinLapTicks()
        {
            return TimeSpan.TicksPerSecond; //1 sec
        }

        public string GetStatus()
        {
            return "";
        }

        #region Lane controls
        public Lane GetLaneById(byte laneId)
        {
            return _lanes[laneId];
        }

        public Lane GetLaneByPin(uint pin)
        {
            foreach (Lane lane in _lanes)
                if (lane.Port.Id == (Cpu.Pin)pin)
                    return lane;

            return null;
        }

        public bool SetLaneVoltage(byte laneId, byte voltageId)
        {
            GetLaneById(laneId).VoltageId = voltageId;
            return true;
        }

        private void OnLapCompleted(Lane lane, LapCompletedEventArgs e)
        {
            Race race = GetRaceByLane(lane);
            //string data = "Lap Completed => RaceId: " + race.Id + " - Pilot: " + s.PilotId + " - LapNumber: " + s.LapCount + " - LapTime: " + e.LapTime.ToString();
            string data = "lapCompleted;" + race.Id + ";" + lane.Id + ";" + lane.PilotId + ";" + lane.LapCount + ";" + e.LapTime.ToString();
            Update(new UpdateEventArgs(e.When, UpdateEventArgs.UpdateType.LapCompleted, data));
        }
        #endregion

        #region Race controls
        public string GetRaces()
        {
            string ret = "";
            foreach (Race race in _races)
                ret += race.Id + "|" + race.State + "-";
            return ret;
        }

        public Race GetRaceById(int raceId)
        {
            foreach (Race race in _races)
                if (race.Id == raceId)
                    return race;
            return null;
        }

        public Race GetRaceByLane(Lane lane)
        {
            foreach (Race race in _races)
                if (race.Contains(lane.Id))
                    return race;
            return null;
        }

        public bool NewRace(int raceId, TimeSpan duration, int[] pilotsIds, byte[] lanesIds)
        {
            try
            {
                //Create the race object
                Race race = new Race(raceId, duration, lanesIds);
                race.OnRaceEnded += new RaceEndedEventHandler(OnRaceEnded);

                //Attach lanes to the OnRaceStateChanged race event
                for (byte i = 0; i < lanesIds.Length; i++)
                {
                    Lane lane = GetLaneById(lanesIds[i]);
                    if (!lane.IsIdle)
                    {
                        throw new ArgumentException("The lane " + i + " is not idle !", "lanesIds");
                    }
                    race.OnRaceStateChanged += new RaceStateChangedEventHandler(lane.RaceStateChanged);
                }

                //Add to the race list
                _races.Add(race);

                return true;
            }
            catch { }

            return false;
        }

        public bool StartRace(int raceId)
        {
            Race race = GetRaceById(raceId);

            if (null == race)
                return false;

            return race.Start();
        }

        public bool PauseRace(int raceId)
        {
            Race race = GetRaceById(raceId);

            if (null == race)
                return false;

            return race.Pause();

        }

        public bool PauseRaces()
        {
            bool res = true;

            foreach (Race race in _races)
            {
                res &= race.Pause();
            }

            return res;
        }

        public bool ResumeRace(int raceId)
        {
            Race race = GetRaceById(raceId);

            if (null == race)
                return false;

            return race.Resume();
        }

        public bool ResumeRaces()
        {
            bool res = true;

            foreach (Race race in _races)
            {
                res &= race.Resume();
            }

            return res;
        }

        public bool EndRace(int raceId, bool aborted = false)
        {
            Race race = GetRaceById(raceId);

            if (null == race)
                return false;

            return race.End(aborted);
        }

        private void OnRaceEnded(Race s, RaceEndedEventArgs e)
        {
            /*//Dettach lanes to the OnRaceStateChanged race event
            byte[] lanesIds = s.LanesIds;
            for (byte i = 0; i < lanesIds.Length; i++)
            {
                Lane lane = GetLaneById(lanesIds[i]);
                s.OnRaceStateChanged -= new RaceStateChangedEventHandler(lane.RaceStateChanged);
            }*/

            //string data = "Race ended => Id: " + s.Id + " - When: " + e.When.ToString() + " - Aborted: " + (e.Aborted ? "Yes" : "No");

            string data = "raceEnded;" + s.Id + ";" + e.When.ToString() + ";" + (e.Aborted ? "True" : "False");
            Update(new UpdateEventArgs(e.When, UpdateEventArgs.UpdateType.RaceEnded, data));
        }

        public bool CloseRace(int raceId)
        {
            Race race = GetRaceById(raceId);

            if (null == race)
                return false;
            
            //Remove from actual races
            _races.Remove(race);
            
            return true;
        }
        #endregion;
    }
    
    #region Event support classes
    public class UpdateEventArgs : EventArgs
    {
        public DateTime When { get; internal set; }
        public UpdateType Type { get; internal set; }
        public string Data { get; internal set; }

        public UpdateEventArgs(DateTime when, UpdateType type)
        {
            When = when;
            Type = type;
        }

        public UpdateEventArgs(DateTime when, UpdateType type, string data)
            : this(when, type)
        {
            Data = data;
        }

        public enum UpdateType
        {
            LapCompleted,
            RaceEnded
        }
    }

    public delegate void UpdateEventHandler(TrackController sender, UpdateEventArgs e);
    #endregion
}
